package com.swak.frame.api.filter.interceptor;

import java.lang.reflect.Method;
import java.util.Collection;

import javax.annotation.Resource;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;

import com.swak.frame.api.filter.SwakServiceChainFactory;
import com.swak.frame.context.BoundContext;
import com.swak.frame.context.ThreadLocalClearUp;
import com.swak.frame.dto.request.SwakRequest;
import com.swak.frame.exception.ApiException;
import com.swak.frame.exception.SwakAssert;
import com.swak.frame.exception.ThrowableWrapper;
import com.swak.frame.filter.SwakMethodUtils;
import com.swak.frame.filter.annotation.ApiFilterOperation;
import com.swak.frame.filter.annotation.SwakMethod;
import com.swak.frame.handler.ResponseHandler;
import com.swak.frame.interceptor.SwakAdviceSupport;
import com.swak.frame.interceptor.SwakOperationInvoker;
import com.swak.frame.logger.AspectLoggerFactory;
import com.swak.frame.ratelimit.annotation.RateLimit;
import com.swak.frame.system.SystemConfigurable;
import com.swak.frame.ump.UmpServiceGateway;

/**
 * AOP Alliance MethodInterceptor for declarative SwakMethod management using
 * the common com.swak.frame.filter.annotation.SwakMethod infrastructure
 * ({@link com.swak.frame.filter.annotation.SwakMethod})
 * 
 * @ClassName: SwakMethodInterceptor.java
 * @author: colley.ma
 * @date: 2022/02/16
 */
public class SwakMethodInterceptor extends SwakAdviceSupport {

	@Resource
	private SystemConfigurable systemConfig;
	
	@Autowired(required = false)
	private UmpServiceGateway umpServiceGateway;

	
	private SwakServiceChainFactory swakServiceChainFactory;
	
	
	public SwakMethodInterceptor(SwakServiceChainFactory swakServiceChainFactory) {
		super();
		this.swakServiceChainFactory = swakServiceChainFactory;
	}

	@Override
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		SwakOperationInvoker aopAllianceInvoker = () -> {
			try {
				return invocation.proceed();
			} catch (Throwable ex) {
				throw new ThrowableWrapper(ex);
			}
		};
		Object target = invocation.getThis();
		SwakAssert.state(target != null, "Target must not be null");
		try {
			return execute(aopAllianceInvoker, target, method, invocation.getArguments());
		} catch (ThrowableWrapper th) {
			throw th.getOriginal();
		}
	}

	@Nullable
	protected Object execute(SwakOperationInvoker invoker, Object target, Method method, Object[] args) {
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in
		// automatically)
		if (this.initialized) {
			Class<?> targetClass = getTargetClass(target);
			ApiFilterOperationSource operationSource = (ApiFilterOperationSource) getOperationSource();
			if (operationSource != null) {
				Collection<ApiFilterOperation> operations = operationSource.getBasicOperations(method,
						targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
					ApiFilterOperation filterOperation = operations.iterator().next();
					return execute(invoker, filterOperation, targetClass, method, (SwakRequest) args[0]);
				}
			}
		}
		return super.invokeOperation(invoker);
	}

	public Object execute(SwakOperationInvoker invoker, ApiFilterOperation filterOperation, Class<?> targetClass,
			Method method, SwakRequest request) {
		Logger filterlogger = AspectLoggerFactory.getLogger(targetClass);
		try {
			SwakMethod swakMethod = filterOperation.getSwakMethod();
			boolean requiredUmp = swakMethod.requiredUmp();
			RateLimit rateLimit = SwakMethodUtils.getSwakMethodRateLimit(swakMethod, systemConfig);
			BoundContext context = new BoundContext(request, swakMethod, filterOperation.getResource(), rateLimit);
			context.setMethod(method);
			context.setTargetClass(targetClass);
			// 执行SPIChain
			swakServiceChainFactory.filterChain().doFilter(context);
			if (requiredUmp && umpServiceGateway!=null) {
				//UMP监控
				return  umpServiceGateway.invoker(invoker, SwakMethodUtils.filerUmpTag, targetClass, method);
			}
			// 扩展能力处理
			return invoker.invoke();
			
		} catch (ApiException e) {
			filterlogger.warn("method={},retCode:{},retMessage:{},trace:{}",
					new Object[] { method.getName(), e.getErrCode(), e.getErrMessage(), e.getMessage() });
			return ResponseHandler.handleSwakResponse(method.getReturnType(), e);
		} catch (Exception e) {
			filterlogger.error("[biz-error]#" + method.getName() + "#" + filterOperation.getSwakMethod() != null
					? filterOperation.getSwakMethod().desc()
					: "接口" + "异常！", e);
			return ResponseHandler.handleSwakResponse(method.getReturnType(), e);
		} finally {
			//ThreadLocal回收
		    ThreadLocalClearUp.clear();
		}
	}

	@Override
	public void afterSingletonsInstantiated() {
		super.afterSingletonsInstantiated();
		//instantiated swakServiceChainFactory and systemConfig
		if(this.swakServiceChainFactory==null) {
			this.swakServiceChainFactory = beanFactory.getBean(SwakServiceChainFactory.class);
		}
		if(this.systemConfig ==null) {
			this.systemConfig = beanFactory.getBean(SystemConfigurable.class);
		}
		this.initialized = true;
	}
}
